const fs = require('fs');
const os = require('os');
const Path = require('path');
const git = require('isomorphic-git');
const http = require('isomorphic-git/http/node');
const Config = require('./config/index');
const { File, SyncFile } = require('ada-util');
const Packer = require('./packer');
const Pager = require('./pager');
const Hooker = require("./hooker");
const defaultHooker = require("./config/hooker");
const Installer = require('./util/installer');
const helper = require('./util/helper');

class Package extends Installer {
    constructor({ path, agent = 'pnpm', tag = 'publish', workspace }) {
        let hooker = new Hooker({});
        defaultHooker(hooker);
        super({ path, agent, hooker, workspace });
        this._tag = tag;
        this._packer = null;
        this._appConfig = null;
        this._currentApp = path === helper.regularPath(process.cwd());
        this._ready = false;
        this._readyPs = null;
        hooker.setContext('packager', this);
    }

    get name() {
        return this._appConfig.name;
    }

    get packer() {
        return this._packer;
    }

    get isReady() {
        return this._ready;
    }

    get isCurrent() {
        return this._currentApp;
    }

    get isRoot() {
        return this.packer.name === 'root';
    }

    get appConfig() {
        return this._appConfig;
    }

    get packerConfig() {
        return this.packer.config;
    }

    get hooker() {
        return this._hooker;
    }

    initPacker(ops) {
        let { name, map } = this._appConfig;
        this._packer = new Packer(this.path, {
            name,
            ...map || {},
            ...ops,
            develop: this._tag === 'develop',
            workspace: this._workspace
        }, this._hooker);
        return this;
    }

    ready() {
        if (!this._ready) {
            if (!this._readyPs) {
                this._readyPs = Promise.resolve().then(() => {
                    let { dependencies = {}, devDependencies = {} } = require(Path.resolve(this.path, './package.json'));
                    let target = Object.assign(devDependencies, dependencies);
                    let list = Reflect.ownKeys(target).map(name => {
                        return { name, version: target[name] };
                    });
                    return this.hasUnInstallModules(list).then(finded => {
                        if (finded) {
                            return this.install();
                        }
                    });
                }).then(() => {
                    this._readyPs = null;
                    this._ready = true;
                    const configFilePath = Path.resolve(this.path, `./.ada.config.js`);
                    const envPath = process.env.BUILD_CONFIG_PATH;
                    let config = null;
                    if (envPath && new File(envPath).exist) {
                        let { distPath, publish } = require(envPath);
                        config = {
                            distPath,
                            apps: publish.apps
                        };
                    }
                    if (new File(configFilePath).exist) {
                        let _config = require(configFilePath);
                        _config.map = _config[this._tag];
                        _config.apps = _config[this._tag].apps || [];
                        _config = Config.getCommonConfig(_config);
                        this._appConfig = Object.assign(_config, config || {});
                    }
                });
            }
            return this._readyPs;
        } else {
            return Promise.resolve();
        }
    }
}

class GitPackage extends Package {
    constructor({ path, agent = 'pnpm', url, version = 'master', hooker, tag, gitUser, gitPass, workspace }) {
        super({ path, agent, hooker, tag, workspace });
        this._url = url;
        this._version = version;
        this._gitUser = gitUser;
        this._gitPass = gitPass;
    }

    get url() {
        return this._url;
    }

    clone() {
        let dir = this.path;
        let url = this.url;
        if (!new File(dir).exist || !new File(Path.resolve(dir, './package.json')).exist) {
            return this.hooker.emit('git-clone-start', { url }).then(() => {
                let dist = Path.resolve(dir, './_tmp');
                return git.clone({
                    fs, http, dir: dist, url, ref: this._version, singleBranch: true, depth: 1,
                    onAuth: () => ({ username: this._gitUser, password: this._gitPass })
                }).then(() => {
                    return this.hooker.emit('git-clone-end', { url }).then(() => {
                        return new File(dist).copyTo(dir).then(() => {
                            return new File(dist).remove();
                        });
                    });
                }).catch(e => {
                    return this.hooker.emit('git-clone-end', { url, error: e });
                });
            });
        } else {
            return Promise.resolve();
        }
    }

    ready() {
        return this.clone().then(() => {
            return super.ready();
        });
    }
}

class PackageManager {
    constructor({ agent = 'pnpm', tag = 'publish' }) {
        let settingPath = Path.resolve(os.homedir(), './.ada-cloud-temp/setting.json');
        let defaultWorkspace = Path.resolve(os.homedir(), './ada-cloud-workspace');
        let workspace = defaultWorkspace;
        let gitUser = '', gitPass = '';
        if (new File(settingPath).exist) {
            try {
                let info = require(settingPath);
                workspace = info.workspace || defaultWorkspace;
                gitUser = info.gitUser;
                gitPass = info.gitPass;
            } catch (e) { }
        } else {
            new SyncFile(settingPath).write(`{
                "workspace":"",
                "gitUser": "",
                "gitPass": "",
                "authKeysPath": "",
                "registryHost": "localhost:8101",
                "managerHost": "",
                "checked":false,
                "defaultDataPath":""
            }`);
            this.hooker.emit('git-auth-undefined', settingPath);
        }
        this._tag = tag;
        this._map = {};
        this._agent = agent;
        this._workspace = `${workspace}/develop`;
        this._clusterWorkspace = workspace;
        this._gitUser = gitUser;
        this._gitPass = gitPass;
        this._pager = null;
        this._config = null;
        this._rootPackager = null;
        this._currentPackager = null;
        this._packagers = [];
        this._ready = false;
        this._readyPs = null;
    }

    get agent() {
        return this._agent;
    }

    get workspace() {
        return this._workspace;
    }

    get config() {
        return this._config;
    }

    get pager() {
        return this._pager;
    }

    get rootPackager() {
        return this._rootPackager;
    }

    get currentPackager() {
        return this._currentPackager;
    }

    get packagers() {
        return this._packagers;
    }

    get rootPacker() {
        return this.rootPackager.packer;
    }

    get currentPackager() {
        return this.currentPackager.packer;
    }

    get packers() {
        return this.packagers.map(a => a.packer);
    }

    _regularPath(path) {
        path = path.replace(/\\/g, '/');
        if (path[path.length - 1] === '/') {
            path = path.substring(0, path.length - 1);
        }
        return path;
    }

    _getPackageInstance({ path, git, version = 'master' }) {
        if (git) {
            if (!this._map[git]) {
                let name = git.split('/').pop().split('.').shift();
                let versionDot = version === 'master' ? '' : `@${version}`;
                let _path = Path.resolve(this.workspace, `./${name}${versionDot}`);
                this._map[git] = new GitPackage({
                    workspace: this._clusterWorkspace,
                    tag: this._tag,
                    path: helper.regularPath(_path),
                    version,
                    agent: this._agent,
                    url: git,
                    gitUser: this._gitUser,
                    gitPass: this._gitPass
                });
            }
            return this._map[git];
        }
        if (path) {
            if (!this._map[path]) {
                this._map[path] = new Package({
                    workspace: this._clusterWorkspace,
                    tag: this._tag,
                    path: helper.regularPath(path),
                    agent: this._agent
                });
            }
            return this._map[path];
        }
    }

    _readyPackage(ops) {
        let pkg = this._getPackageInstance(ops);
        if (!pkg.isReady) {
            return pkg.ready().then(() => {
                return (pkg.appConfig.apps || []).reduce((a, app) => {
                    return a.then(() => {
                        return this._readyPackage(app);
                    });
                }, Promise.resolve());
            });
        } else {
            return Promise.resolve();
        }
    }

    _getCurrentPackager() {
        let current = Reflect.ownKeys(this._map).find(a => this._map[a].isCurrent);
        return this._map[current].initPacker();
    }

    _readyProcess() {
        return this._readyPackage({ path: process.cwd() }).then(() => {
            let map = {};
            Reflect.ownKeys(this._map).forEach(key => {
                let info = this._map[key];
                map[info.path] = info;
            });
            this._map = map;
            let root = this.getPackager('root');
            if (root) {
                let proxy = [];
                let current = this._getCurrentPackager().initPacker();
                let { name, ssr, server } = current.appConfig;
                let distPath = Path.resolve(current.packerConfig.distPath, './../');
                let result = {
                    name,
                    distPath,
                    ssr,
                    server
                };
                Reflect.ownKeys(this._map).filter(a => this._map[a].name !== current.name).map(name => {
                    let packager = this._map[name];
                    let { server } = packager.appConfig;
                    if (server && server.proxy) {
                        proxy = [...proxy, ...server.proxy];
                    }
                    packager.initPacker({ distPath });
                });
                let proxys = [...proxy, ...result.server.proxy];
                let proxysMap = {};
                proxys.forEach(({ path, option }) => {
                    let key = path.sort((a, b) => a.localeCompare(b)).join(',');
                    if (!proxysMap[key]) {
                        proxysMap[key] = {};
                    }
                    Object.assign(proxysMap[key], option);
                });
                result.server.proxy = Reflect.ownKeys(proxysMap).map(key => {
                    return {
                        path: key.split(','),
                        option: proxysMap[key]
                    };
                });
                this._rootPackager = root;
                this._currentPackager = current;
                this._packagers = Reflect.ownKeys(this._map).map(a => this._map[a]);
                this._packagers.forEach(packer => {
                    packer.packer.config.watchNodeModules = current.packer.config.watchNodeModules;
                });
                this._pager = new Pager({
                    ...current.packerConfig,
                    siteInfo: root.appConfig.info || {},
                    siteURL: '/',
                    distPath,
                    projectPath: root.packerConfig.projectPath,
                    develop: root.packerConfig.develop,
                    apps: this.packers,
                    hooker: current.hooker,
                    gitUser: this._gitUser,
                    gitPass: this._gitPass
                });
                this._config = result;
            } else {
                throw Error('can not find root app');
            }
        });
    }

    getPackager(name) {
        let t = Reflect.ownKeys(this._map).find(a => this._map[a].name === name);
        if (t) {
            return this._map[t];
        }
        return null;
    }

    getPacker(name) {
        let packager = this.getPackager(name);
        if (packager) {
            return packager.packer;
        }
        return null;
    }

    ready() {
        if (!this._ready) {
            if (!this._readyPs) {
                this._readyPs = this._readyProcess().then(() => {
                    this._ready = true;
                    this._readyPs = null;
                });
            }
            return this._readyPs;
        } else {
            return Promise.resolve();
        }
    }

    cleanCache() {
        let target = Path.resolve(this._clusterWorkspace, './.cache/frontend');
        let map = Path.resolve(target, './.ada.cache.map');
        if (new File(map).exist) {
            let cache = JSON.parse(new SyncFile(map).read());
            let all = Reflect.ownKeys(cache).map(a => cache[a]);
            return new File(target).getAllSubFilePaths().then(paths => {
                return paths.reduce((a, b) => {
                    return a.then(() => {
                        if (all.indexOf(Path.basename(b)) == -1) {
                            new File(b).remove();
                        }
                    });
                }, Promise.resolve());
            });
        }
    }
}

module.exports = PackageManager;